package com.kob.backend.consumer;

import com.alibaba.fastjson2.JSONObject;
import com.kob.backend.consumer.utils.Game;
import com.kob.backend.consumer.utils.JwtAuthentication;
import com.kob.backend.mapper.BotMapper;
import com.kob.backend.mapper.RecordMapper;
import com.kob.backend.mapper.UserMapper;
import com.kob.backend.pojo.Bot;
import com.kob.backend.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;

@Component
@ServerEndpoint("/websocket/{token}")  // 表明映射到前端哪个链接，这里也是用jwt验证身份，注意不要以'/'结尾
public class WebSocketServer {

    //与线程安全有关的哈希表（每个实例在线程里面，所以要线程安全），将userID映射到相应用户的WebSocketServer
    // 需要一个对所有websocket可见的全局变量(static实现，和对象无关，绑定在类上)，存储所有的链接
    public static ConcurrentHashMap<Integer, WebSocketServer> users = new ConcurrentHashMap<>();

    //当前链接请求的用户
    private User user;

    public Game game;//这样，每一个链接（每一个WebSocketServer对象）都有自己的地图类game
    private Session session = null;//不同于http协议中的session，每个链接都是用session来维护

    private final static String addPlayerUrl = "http://127.0.0.1:3001/player/add/";
    private final static String removePlayerurl = "http://127.0.0.1:3001/player/remove/";

    /**********因为websocket不是spring组件，所以不能直接单例注入，需要在setter注入静态变量****/
    public static UserMapper userMapper = null;//用类名来访问
    public static RecordMapper recordMapper = null;//用类名来访问(注意，被其他类引用，一定要改成public，不然用不了)
    public static RestTemplate restTemplate = null;//springboot之间进行通信
    public static BotMapper botMapper = null;//取出数据库bot信息

    @Autowired
    public void setUserMapper(UserMapper userMapper){
        WebSocketServer.userMapper = userMapper;//用类名来访问
    }
    @Autowired
    public void setRecordMapper(RecordMapper recordMapper){
        WebSocketServer.recordMapper = recordMapper;//用类名来访问
    }
    @Autowired
    public void setRestTemplate(RestTemplate restTemplate) {
        WebSocketServer.restTemplate = restTemplate;
    }
    @Autowired
    public  void setBotMapper(BotMapper botMapper) {
        WebSocketServer.botMapper = botMapper;
    }

    /************注入在上面****************/




    @OnOpen
    public void onOpen(Session session, @PathParam("token") String token) throws IOException {
        // 建立连接，触发此函数
        System.out.println("connected!");
        this.session = session;
        //为了方便调试，初阶段只把token当成userId看
        Integer userId = JwtAuthentication.getUserId(token);//调用工具类获取UserId
        this.user = userMapper.selectById(userId);
        if (this.user != null) {
            users.put(userId, this);//用户存在，就是合法建立连接
        } else {
            this.session.close();//用户不合法，直接关闭连接
        }
        System.out.println(users);//调试看看所有链接
    }

    @OnClose
    public void onClose() {
        // 关闭链接，触发此函数
        System.out.println("disconnected!");
        //断开连接的话要将user移除
        if (this.user != null) {
            users.remove((this.user.getId()));//从全局哈希表中删除该链接
//            matchpool.remove(this.user);//把该用户从匹配池中删除
        }
    }

    public static void startGame(Integer aId,Integer aBotId,Integer bId,Integer bBotId){
        User a = userMapper.selectById(aId);
        User b = userMapper.selectById(bId);

        Bot bota = botMapper.selectById(aBotId),botb=botMapper.selectById(bBotId);//取出数据库两个bot

        Game game = new Game(13, 14, 20, a.getId(),bota, b.getId(),botb);

        game.createMap();

        if(users.get(a.getId())!=null)
            users.get(a.getId()).game=game;//把当前地图对象，绑定到两个链接中
        if(users.get(b.getId())!=null)
            users.get(b.getId()).game=game;

        game.start();//start是Thread函数的一个API，是另起一个线程执行函数的入口


        JSONObject respGame = new JSONObject();//创建json对象，把重叠的信息封装在一起，方便发送
        respGame.put("a_id", game.getPlayerA().getId());//封装玩家id
        respGame.put("a_sx", game.getPlayerA().getSx());//横纵坐标
        respGame.put("a_sy", game.getPlayerA().getSy());
        respGame.put("b_id", game.getPlayerB().getId());
        respGame.put("b_sx", game.getPlayerB().getSx());
        respGame.put("b_sy", game.getPlayerB().getSy());
        respGame.put("map", game.getG());//地图

        JSONObject respA = new JSONObject();
        respA.put("event", "start-matching");
        respA.put("opponent_username", b.getUsername());
        respA.put("opponent_photo", b.getPhoto());
        respA.put("game", respGame);//发送重叠信息
        if(users.get(a.getId())!=null)
            users.get(a.getId()).sendMessage(respA.toJSONString());

        JSONObject respB = new JSONObject();
        respB.put("event", "start-matching");
        respB.put("opponent_username", a.getUsername());
        respB.put("opponent_photo", a.getPhoto());
        respB.put("game", respGame);//发送重叠信息
        if(users.get(b.getId())!=null)
            users.get(b.getId()).sendMessage(respB.toJSONString());
    }
    private void startMatching(Integer botId) {
        System.out.println("start matching!");
        MultiValueMap<String, String> data = new LinkedMultiValueMap<>();//必须用这个一键多值的哈希表
        data.add("user_id", this.user.getId().toString());//传id和分数
        data.add("rating", this.user.getRating().toString());
        data.add("bot_id",botId.toString());
        restTemplate.postForObject(addPlayerUrl, data, String.class);//发送请求的函数，参数依次为：url,数据,匹配系统返回值类型
    }
    private void stopMatching() {
        System.out.println("stop matching");
        MultiValueMap<String, String> data = new LinkedMultiValueMap<>();
        data.add("user_id", this.user.getId().toString());
        restTemplate.postForObject(removePlayerurl, data, String.class);
    }


    private void move(int direction) {
        if (game.getPlayerA().getId().equals(user.getId())) {//如果是A玩家
            if (game.getPlayerA().getBotId().equals(-1))  // 如果亲自出马，才要读取人工下一步操作
                game.setNextStepA(direction);//调用写入nextstep的函数
        } else if (game.getPlayerB().getId().equals(user.getId())) {//如果是B玩家
            if (game.getPlayerB().getBotId().equals(-1))  // 如果亲自出马，才要读取人工下一步操作
                game.setNextStepB(direction);//调用写入nextstep的函数
        }
    }
    @OnMessage
    public void onMessage(String message, Session session) {//当作路由，根据从前端传来的message判断之后执行哪一个函数
        // 从Client接收消息
        System.out.println("receive message!");
        JSONObject data = JSONObject.parseObject(message);//字符串转换成json
        String event = data.getString("event");//从json中获取enent，判断当前是在开始匹配，还是结束匹配
        if ("start-matching".equals(event)) {
            startMatching(data.getInteger("bot_id"));//把开始匹配的业务封装成一个函数
        } else if ("stop-matching".equals(event)) {
            stopMatching();//把结束匹配的业务封装成一个函数
        }else if ("move".equals(event)) {//前端向后端发送信息，事件类型是move
            move(data.getInteger("direction"));//调用move(方向)
        }
    }

    @OnError
    public void onError(Session session, Throwable error) {
        error.printStackTrace();
    }

    public void sendMessage(String message){//自定义一个发送信息的函数（websocket都是异步通信过程，所以发信息要加锁）
        synchronized (this.session) { //加锁
            try {
                this.session.getBasicRemote().sendText(message); //从后端向当前链接发送信息
            } catch (IOException e) { //IO异常
                e.printStackTrace();
            }
        }
    }
}

